[Kotlin]レシーバー指定ラムダとは何か
まえがき
Kotlinのスコープ関数便利ー。
let apply run with also
ただ本当に使いこなすには ラムダ と レシーバー指定ラムダ を理解しなければなりません。
// ラムダ public inline fun T.let(block: (T) -> R): R = block(this)
// レシーバー指定ラムダ public inline fun T.apply(block: T.() -> Unit): T { block(); return this }
block: (T) -> R) と block: T.() -> Unit の違いわかりますか
これを理解しないとblock内の this の罠に引っかかる
サンプル
理解するためのサンプルコードを書きました
class Sample { val lambda: (String) -> Unit = { Log.d("test", this.javaClass.simpleName) //Sample Log.d("test", it) //test } val receiverLambda: String.() -> Unit = { Log.d("test", this.javaClass.simpleName) //String Log.d("test", this) //test } }
thisの値が違いますね。なぜこういう動きになるのかKotlin BytecodeをDecompileにして、おってみましょう。
Kotlin BytecodeからDecompile
Android Studioをもっていれば簡単にKotlin BytecodeをDecompileできます。
Decompileしたものがこちら。
// ... (Java decompiled code)
ラムダ
// ... (Java decompiled code for lambda)
ラムダは、Function1というインターフェースがつくられ、invokeという関数ができます。itという名のStringを引数なっていますね。
thisはFunction1インターフェースを実装した場所すなわち、Sampleになります。
レシーバー指定ラムダ
// ... (Metadata for receiverLambda)
ExtensionFunctionTypeつまり、拡張関数です。この場合はStringにgetReceiverLambdaというメソッドを作っていますね
このFunction1の定義場所はStringなのでthisはStringになります。
こんなこともできちゃいます。
class Sample { //この関数内で使用できる拡張関数。Stringにlog()を拡張 fun log(log: String.() -> Unit) { "test".log() "hoge".log() "foo".log() } fun showLog() { log { print(this) } log { Log.d("hoge", this) } } }
fun log()内だけで有効なString拡張関数をいれるイメージです。これを利用するとKotlinっぽいDSLが作りやすくなりますので、何度サンプルを書いて試してみることをおすすめします。
まとめ
ラムダとレシーバー指定ラムダはなんとなくイメージできましたか?
レシーバーって結局なんだ?っと思った方いるかもしれません。それは次回の話のネタに。